package pl.donajski.magisterka;

import java.util.ArrayList;

public class Substitution implements IMove {

	
	public Substitution(Task task, Node newNode, float penaltyRatio, int sumOfProcessingRateMax) {
		this.sNode = task.getSourceNode();
		this.nNode = newNode;
		this.oNode = task.getAssignment();
		this.task = task;
		this.penaltyRatio = penaltyRatio;
		this.sumOfProcessingRateMax = sumOfProcessingRateMax;
	}

	public void move(State s) {
		if(oNode.equals(nNode))
			return;
		this.setFlows(s);
		this.setProcessing(s);	
	}
	
	public int getCostAfterMove(State s) {
		if(oNode.equals(nNode))
			return s.getCost();
		return this.mCostAfterMove = s.getCost() + this.getFlowCostChange(s) + this.processingCostChange();
	}
	
	public IMove clone() {
		return new  Substitution(task, nNode, penaltyRatio, sumOfProcessingRateMax);
	}



	private void setFlows(State s) {
		/*
		 * if sNode == oNode or sNode == nNode we need to include changes in both nodes:
		 * let new source node flow be equal to old source node flow + x, x is now the
		 * difference, next let new old node flow be equal to old old node flow + y, y is
		 * now the difference between old and new flow, source and old node flow is equal 
		 * to old flow + (x + y), than it is exactly 
		 * this.getNewDSourceFlow(s) + this.getNewDOldFlow(s) - oNode.getDFlow() = 
		 * oldflow + x + oldflow + y - oldflow = oldflow + (x + y)
		 * NOTE: there is no need to consider it in case of download flow since then y = 0
		 */
		if(sNode.equals(oNode)) {
			sNode.setUFlow(this.getNewUSourceFlow(s) + this.getNewUOldFlow(s) - sNode.getUFlow());
			oNode.setUFlow(sNode.getUFlow());
			nNode.setUFlow(this.getNewUNewFlow(s));
			
		} else if(sNode.equals(nNode)) {
			sNode.setUFlow(this.getNewUSourceFlow(s) + this.getNewUNewFlow(s) - sNode.getUFlow());
			nNode.setUFlow(sNode.getUFlow());
			oNode.setUFlow(this.getNewUOldFlow(s));
		} else {
			sNode.setUFlow(this.getNewUSourceFlow(s));
			oNode.setUFlow(this.getNewUOldFlow(s));
			nNode.setUFlow(this.getNewUNewFlow(s));
		}
		
		sNode.setDFlow(this.getNewDSourceFlow(s));
		oNode.setDFlow(this.getNewDOldFlow(s));
		nNode.setDFlow(this.getNewDNewFlow(s));
	}
	
	private void setProcessing(State s) {
		task.setAssignment(nNode);
	}
	
	public int getValidnessAfterMove(State s) {
		int current = s.getValidness();
		int oNodeValidness = oNode.getValidness();
		
		if(!nNode.canAssign())
			current++;
		
		if(oNodeValidness > 0)
			current--;
		
		return current;
		
	}
	
	private int getFlowCostChange(State s) {
		/*
		 * first letter = upload / download
		 * second letter = time indicator (o - old, n - new)
		 * third letter = node indicator (o - old node, n - new node, s - source node)
		 * examlpe:
		 * 			upload flow before move (old flow) in node to be replaced by this move (old node)
		 *			- uooflow
		 *			download flow after move (new flow) in node to be replaced by this move (old node)
		 *			- dnoflow 
		 */
		int uosflow	 	= 0, dosflow	= 0,
			unsflow		= 0, dnsflow	= 0, 
			uooflow		= 0, dooflow	= 0,
			unoflow 	= 0, dnoflow 	= 0, 
			uonflow 	= 0, donflow 	= 0,
			unnflow 	= 0, dnnflow 	= 0,
			oscost		= 0,
			nscost 		= 0,
			oocost		= 0,
			nocost 		= 0,
			oncost		= 0,
			nncost 		= 0;
			
			uosflow = sNode.getUFlow();
			uooflow = oNode.getUFlow();
			uonflow = nNode.getUFlow();
			unsflow = getNewUSourceFlow(s);
			unoflow = getNewUOldFlow(s);
			unnflow = getNewUNewFlow(s);
			
			dosflow = sNode.getDFlow();
			dooflow = oNode.getDFlow();
			donflow = nNode.getDFlow();
			dnsflow = getNewDSourceFlow(s);
			dnoflow = getNewDOldFlow(s);
			dnnflow = getNewDNewFlow(s);
			
			/*
			 * if sNode == oNode or sNode == nNode we need to include changes in both nodes:
			 * let new source node flow be equal to old source node flow + x, x is now the
			 * difference, next let new old node flow be equal to old old node flow + y, y is
			 * now the difference between old and new flow, source and old node flow is equal 
			 * to old flow + (x + y), than it is exactly 
			 * this.getNewDSourceFlow(s) + this.getNewDOldFlow(s) - oNode.getDFlow() = 
			 * oldflow + x + oldflow + y - oldflow = oldflow + (x + y)
			 * NOTE: there is no need to consider it in case of download flow since then y = 0
			 */
			if(sNode.equals(oNode)) {
				oscost = 0;
				nscost = 0;
				oocost = getMaxFowCost(uooflow, dooflow, oNode.getCapacityModuleSize(), oNode.getCapacityModuleOpex());
				nocost = getMaxFowCost(unoflow + unsflow - uooflow,  dnoflow + dnsflow - dooflow, oNode.getCapacityModuleSize(), oNode.getCapacityModuleOpex());
				oncost = getMaxFowCost(uonflow,  donflow, nNode.getCapacityModuleSize(), nNode.getCapacityModuleOpex());
				nncost = getMaxFowCost(unnflow, dnnflow, nNode.getCapacityModuleSize(), nNode.getCapacityModuleOpex());
				
				if(App._debug)
					print( uosflow, dosflow,
							unsflow, dnsflow,
							uooflow, dooflow, 
							unoflow + unsflow - uooflow, dnoflow + dnsflow - dooflow, 
							uonflow,  donflow,
							unnflow, dnnflow);
					
			} else if(sNode.equals(nNode)) {
				oscost = 0;
				nscost = 0;
				oncost = getMaxFowCost(uonflow,  donflow, nNode.getCapacityModuleSize(), nNode.getCapacityModuleOpex());
				nncost = getMaxFowCost(unnflow + unsflow - uonflow, dnnflow + dnsflow - donflow, nNode.getCapacityModuleSize(), nNode.getCapacityModuleOpex());
				oocost = getMaxFowCost(uooflow, dooflow, oNode.getCapacityModuleSize(), oNode.getCapacityModuleOpex());
				nocost = getMaxFowCost(unoflow, dnoflow, oNode.getCapacityModuleSize(), oNode.getCapacityModuleOpex());
				
				if(App._debug)
					print( uosflow, dosflow,
							unsflow, dnsflow,
							uooflow, dooflow,
							unoflow, dnoflow,
							uonflow,  donflow,
							unnflow + unsflow - uonflow, dnnflow + dnsflow - donflow);
			} else {
				oscost = getMaxFowCost(uosflow, dosflow, sNode.getCapacityModuleSize(), sNode.getCapacityModuleOpex());
				nscost = getMaxFowCost(unsflow, dnsflow, sNode.getCapacityModuleSize(), sNode.getCapacityModuleOpex());
				oncost = getMaxFowCost(uonflow, donflow, nNode.getCapacityModuleSize(), nNode.getCapacityModuleOpex());
				nncost = getMaxFowCost(unnflow, dnnflow, nNode.getCapacityModuleSize(), nNode.getCapacityModuleOpex());
				oocost = getMaxFowCost(uooflow, dooflow, oNode.getCapacityModuleSize(), oNode.getCapacityModuleOpex());
				nocost = getMaxFowCost(unoflow, dnoflow, oNode.getCapacityModuleSize(), oNode.getCapacityModuleOpex());
				
				if(App._debug)
					print( uosflow, dosflow,
							unsflow, dnsflow,
							uooflow, dooflow,
							unoflow, dnoflow,
							uonflow, donflow,
							unnflow, dnnflow);
			}
			
			
			
			/* return sum of cost differences */
			return (nscost - oscost) + (nocost - oocost) + (nncost - oncost);
			
	}


	private void print(int i, int j, int k, int l, int m, int n,
			int o, int p, int r, int s, int t, int u) {

		System.out.println(sNode.getIndex() + ":\t" + i + "\t" + j + "\t" + k + "\t" + l);
		System.out.println(oNode.getIndex() + ":\t" + m + "\t" + n + "\t" + o + "\t" + p);
		System.out.println(nNode.getIndex() + ":\t" + r + "\t" + s + "\t" + t + "\t" + u);
		
		
	}

	private int getMaxFowCost(int flow1, int flow2, int capacityModuleSize,
			int capacityModuleOpex) {
		
		return Math.max( (int) Math.ceil((float) ( flow1 ) / capacityModuleSize) * capacityModuleOpex,
				   (int) Math.ceil((float) ( flow2 ) / capacityModuleSize) * capacityModuleOpex);
	}

	private int getNewDNewFlow(State s) {
		/*
		 * new flow of new node is equal to it's old flow corrected by:
		 * - task's input transmission download if it is not source node
		 * - task's output transmission download if it is one of task's destination nodes
		 */
		int nnflow = nNode.getDFlow();
		
		if(!sNode.equals(nNode)) {
			nnflow += task.getInputTransmissionRate();
		}
		
		if(task.destinationContains(nNode)) {
			nnflow -= task.getOutputTransmissionRate();
		}
		
		return nnflow;
	}

	private int getNewDOldFlow(State s) {
		/*
		 * new flow of old node is equal to it's old flow corrected by:
		 * - task's input transmission download if it is not source node
		 * - task's output transmission download if it is one of task's destination nodes
		 */
		int noflow = oNode.getDFlow();
		
		if(!oNode.equals(sNode)) {
			noflow -= task.getInputTransmissionRate();
		}
		
		if(task.destinationContains(oNode)) {
			noflow += task.getOutputTransmissionRate();
		}
		
		return noflow;
	}

	private int getNewDSourceFlow(State s) {
		/* source node download remains unchanged except
		 * of download changes in new and old node if equal 
		 * to source node
		 */
		return sNode.getDFlow();
	}



	private int getNewUNewFlow(State s) {
		int nnflow = nNode.getUFlow();
				
		nnflow += task.getOutputTransmissionRate() * task.destinationSize();
		/* excluding itself factor */
		if(task.destinationContains(nNode)) {
			nnflow -= task.getOutputTransmissionRate();
		}
		
		return nnflow;
	}

	private int getNewUOldFlow(State s) {
		/*
		 * OLD NODE:
		 *  - noflow = ooflow - (output uploading flows [excluding itself!!!])
		 */
		int noflow = oNode.getUFlow();
		
		noflow -= task.getOutputTransmissionRate() * task.destinationSize();
		/* excluding itself factor */
		if(task.destinationContains(oNode)) {
			noflow += task.getOutputTransmissionRate();
		}
		
		return noflow;
	}

	private int getNewUSourceFlow(State s) {
		/*
		 * SOURCE NODE:
		 * flows related to sending task's input data
		 * 	- if new node equals source node then sourceFlow -= taskInput
		 *  - if old node equals source node then sourceFlow += taskInput
		 */
		int nsflow = sNode.getUFlow();
		
		if(sNode.equals(nNode)) {
			nsflow -= task.getInputTransmissionRate();
		}
		if(oNode.equals(sNode)) {
			nsflow += task.getInputTransmissionRate();
		}
		
		return nsflow;
	}
	
	private int processingCostChange() {
		return nNode.getProcessingOpex() - oNode.getProcessingOpex(); 
	}

	public int getPenaltyAfterMove(State s) {
		float p = this.calcPenalty(s);
		return (int) ((float)p * this.mCostAfterMove);
	}
	
	private float calcPenalty(State s) {
		int c = this.getValidnessAfterMove(s);
		
		float ret = (float) c / this.sumOfProcessingRateMax;
		return this.penaltyRatio*ret;
	}

	public Node getNewNode() {
		return nNode;
	}
	
	public Node getOldNode() {
		return oNode;
	}

	private int mCostAfterMove = 0;
	private Node sNode;
	private Node nNode;
	private Node oNode;
	private Task task;
	private int sumOfProcessingRateMax = 0;
	private float penaltyRatio = 0f;
	
}
